#include "Registration.hpp"

#include "Logger.hpp"

#include <math.h>
#include <assert.h>
#include <iostream>

#include <boost/thread/thread.hpp>
#include <boost/foreach.hpp>
#define foreach         BOOST_FOREACH
#define reverse_foreach BOOST_REVERSE_FOREACH

#include <girlTools/girlToolsJpgCodec.hpp>

#include <boost/progress.hpp>



/***********************************************************************************************************/
/***********************************************************************************************************/
/***********************************************************************************************************/
/*** Registration */

Registration::Registration()
{}


Registration::Registration(const girl::tools::Image &image1, const girl::tools::Image &image2)
{

//    assert(image1.width() == image2.width());
//    assert(image1.height() == image2.height());

    boost::timer t;


    int width =  (image1.width() < image2.width()) ? image1.width() : image2.width();
    int height =  (image1.height() < image2.height()) ? image1.height() : image2.height();

    girl::tools::Image image(width,height,image1.format());
    _verso = image;
    _recto = image;



    for(int i = 0 ; i < width; i++)
    {
        for(int j=0; j < height; j++)
        {
            //std::cout << i << " image2 " << image2.width() << " j "<< j << " "<< i-w_length << " " << j-h_length << std::endl;


            _verso.setPixel(i,j,image2.getPixel(i,j));
            _recto.setPixel(i,j,image1.getPixel(i,j));
        }
    }
}


double timeForDiff = 0;

Transformation * Registration::registerTransformation()
{
    boost::timer registrationTimer;

    Registration::DiffObj originalDiff(diff(_recto,_verso), Transformation::TransformationParameter());


    boost::thread_group group;
    std::vector<ThreadTransformationAndDiff *> toBeThreads;


    //rotations
    for(int i = -9; i <= 9 ; i++)
    {
        Transformation::TransformationParameter initialParameters;
        initialParameters.angle = i; initialParameters.dx = 0; initialParameters.dy=0;

        ThreadTransformationAndDiff * diffThread = new ThreadTransformationAndDiff(
                _verso,originalDiff.getDiff(),_recto,initialParameters);
        toBeThreads.push_back(diffThread);

        group.create_thread(boost::bind(&ThreadTransformationAndDiff::run, diffThread));

    }

    group.join_all();

    std::cout << "registration cost : " << registrationTimer.elapsed() << std::endl;
    std::cout << "diff cost : " << timeForDiff << std::endl;

    DiffObj finalMinDiff = originalDiff;
    foreach(ThreadTransformationAndDiff * diffT, toBeThreads)
    {
        Registration::DiffObj d =  diffT->getDiff();
        if(d.getDiff() < finalMinDiff.getDiff() )
        {
            finalMinDiff = d;
        }
    }
    foreach(ThreadTransformationAndDiff * diffT, toBeThreads)
    {
        delete diffT;
    }
    toBeThreads.clear();



    std::cout << "minDiff = " << finalMinDiff.getTransformationParameters().dx
            << " yy " << finalMinDiff.getTransformationParameters().dy
            << "rotation " << finalMinDiff.getTransformationParameters().angle << std::endl;



    return finalMinDiff.getTransformationParameters().create(_verso);
}




double Registration::diff(const girl::tools::Image &image1,const girl::tools::Image &image2,int step)
{

    boost::timer t;

    assert(image1.width()==image2.width());

    double diff = 0;

    for(int i = 0 ; i < image1.width(); i+=step)
    {
        for(int j = 0 ; j < image1.height(); j+=step)
        {
            diff += abs(image1.getPixel(i,j).a() - image2.getPixel(i,j).a());
        }
    }

    timeForDiff+=t.elapsed();


    return diff;
}

/**
  * if not vertical then horizontal
  */
Registration::DiffObj Registration::getMinDiff(Transformation &t,
                                               Transformation::TransformationParameter & params,
                                               int range)
{

    std::cout << "deprecated" << std::endl;
//    t.setParams(params);
//    int original_diff=diff(_recto,t.transformed());
//    Registration::DiffObj mindiff(original_diff,params);

//    std::cout << "mindiff " << original_diff << std::endl;

//    t.setParams(params);
//    for(int i = -1*range ; i <= range ; i++)
//    {

//        //std::cout << "ici " << i << std::endl;
//        t.setParam(i);
//        girl::tools::Image image = t.transformed();

//        double tmp = diff(_recto,image);
//        std::cout << tmp << std::endl;
//        if(tmp < mindiff.getDiff())
//        {

//            mindiff.saveDiff(tmp);
//            mindiff.saveTransformationParameters( t.getParam());
//            params=t.getParam();
//            //std::cout << "min " << t.getParam().dx << " " << t.getParam().dy << std::endl;
//            //std::cout << "min " << mindiff.params->dx << " " <<mindiff.params->dy << std::endl;
//            //std::cout << "et la" << std::endl;
//        }
//    }


    //return mindiff;
}





/***********************************************************************************************************/
/***********************************************************************************************************/
/***********************************************************************************************************/
/*** Registration :: ThreadTransformationAndDiff */

#include <sstream>


Registration::ThreadTransformationAndDiff::ThreadTransformationAndDiff(
        girl::tools::Image image,
        int diffOriginal,
        const girl::tools::Image &recto,
        Transformation::TransformationParameter initialParams)
            :
            _verso(image),
            _recto(recto),
            _initialParameters(initialParams),
            _finalMinDiff(diffOriginal,initialParams)
{}

void Registration::ThreadTransformationAndDiff::run()
{
    //trans H
   for(int j = -10; j <= 10 ; j++)
   {

       _initialParameters.dx = j;

        //girl::tools::Image transH = AffineTransform::translated(_verso,1,0);
//        girl::tools::JpgCodec codec;
//        std::stringstream stream;
//        stream << "test_" << j << ".jpg" ;
//        std::string str;
//        stream >> str;
//        codec.save(str,transH);

        //trans V
       for(int l = -10; l <= 10; l++)
        {
           _initialParameters.dy = l;

           Transformation trans(_verso);
            trans.setParams(_initialParameters);

           girl::tools::Image transImage = trans.transformed();


//           girl::tools::JpgCodec codec;
//           std::stringstream stream;
//           stream << "test_" << trans.getParam().angle << "_" << trans.getParam().dx << "_" << trans.getParam().dy << ".jpg" ;
//           std::string str;
//           stream >> str;
//           codec.save(str,transImage);

           //int toto;
           Registration::DiffObj mindiff1(Registration::diff(_recto,transImage),_initialParameters);


         //  std::cout << "j " << j << " l " << l << "   "  << mindiff1.diff << std::endl;


           if(_finalMinDiff.getDiff() > mindiff1.getDiff()){
               _finalMinDiff = mindiff1;
           }
        }
    }
    //std::cout << "minDiff = " << _finalMinDiff.params->dx << " " << _finalMinDiff.params->dy << "rotation " << _finalMinDiff.params->angle << std::endl;
}


/***********************************************************************************************************/
/***********************************************************************************************************/
/***********************************************************************************************************/
/*** BlockRegistration */


BlockRegistration::BlockRegistration(const girl::tools::Image &image1, const girl::tools::Image &image2)
    : Registration(image1,image2)//,
    //_originalVerso(image2)
{
    boost::timer t;

    createBlocks(image1,image2,blocks);

    std::cout << "time for preparation : " << t.elapsed() << std::endl;
}


Transformation * BlockRegistration::registerTransformation()
{

    Registration::DiffObj finalMinDiff(-1,Transformation::TransformationParameter());
    foreach(block b , blocks)
    {
        _recto = b.recto;
        _verso = b.verso;
        Transformation * t = Registration::registerTransformation();
        std::cout << "angle "<< t->getParam().angle << " dx "<< t->getParam().dx << " dy " <<t->getParam().dy << std::endl;
        int difference = diff(_recto,_verso,1);
        if( difference < finalMinDiff.getDiff() ||  finalMinDiff.getDiff() == -1)
        {
            finalMinDiff.saveDiff( difference);
            finalMinDiff.saveTransformationParameters(t->getParam());
        }

        delete t;
    }
    return finalMinDiff.getTransformationParameters().create(_originalVerso);
}

void BlockRegistration::getPreparedBlock(const girl::tools::Image &image1,
                 const girl::tools::Image &image2,
                 int x1,int y1,
                 int x2,int y2,
                 block &b)
{
    int w = x2-x1;
    int h = y2-y1;


    std::cout << "x1 " << x1 << "x2 " << x2 << std::endl;

    std::cout << "y1 " << y1 << "y2 " << y2 << std::endl;

    std::cout << "w " << w << "h " << h << std::endl;

    girl::tools::Image image(w,h,image1.format());
    b.recto= image;
    b.verso= image;


    for(int i = x1; i < x2; i++)
    {
        for(int j=y1; j < y2; j++)
        {

            //std::cout << " i " << i << " j " << " pixel " << image1.getPixel(i,j).a()  <<std::endl;
            b.recto.setPixel(i-x1,j-y1,image1.getPixel(i,j));

            b.verso.setPixel(i-x1,j-y1,image2.getPixel(i,j));
        }

    }
    /*girl::tools::JpgCodec codec;
    std::stringstream stream;
    stream << "block1Verso_" << x1 << "_" << x2 <<"_" << ".jpg" ;
    std::string str;
    stream >> str;
    codec.save(str,blockVerso);*/

}

void BlockRegistration::createBlocks(const girl::tools::Image &image1,
                  const girl::tools::Image &image2,
                  std::vector<block> &blocks)
{
    int space_w= image1.width()*10 / 100; //(5%)
    int space_h = image1.height()*20 / 100;//(5%)

    std::cout << "space_w " << space_w << std::endl;
    std::cout << "image1.width " << image1.width() << std::endl;
    int block_size_w = (image1.width()  - space_w*4) / 3;
    std::cout << "block_ size _ w " << block_size_w << std::endl;
    int block_size_h = (image1.height() - space_h*4) / 2;

    //boost::thread_group group;



    for(int i = 1; i <=3; i++)
    {
        for(int j =1;j <=2; j++)
        {
            int x1 = i*space_w;
            int y1 = space_h;
            int x2 = x1+block_size_w;
            int y2 = y1+block_size_h;
            block b;

            //group.create_thread(boost::bind(&createBlock,image1,image2,x1,y1,x2,y2,blockRecto1,blockVerso1));
            getPreparedBlock(image1,image2,x1,y1,x2,y2,b);
            blocks.push_back(b);
        }
    }
    //group.join_all();
}

/***********************************************************************************************************/
/***********************************************************************************************************/
/***********************************************************************************************************/
/*** CenteredBlockRegistration */

const int CenteredBlockRegistration::REMOVED_BORDERS_PERCENTAGE = 5;

CenteredBlockRegistration::CenteredBlockRegistration(const girl::tools::Image &image1, const girl::tools::Image &image2)
{
    int w_length= image1.width()*REMOVED_BORDERS_PERCENTAGE / 100;
    int h_length = image1.height()*REMOVED_BORDERS_PERCENTAGE / 100;


    girl::tools::Image image(image1.width() - 2*w_length,image1.height()-2*h_length,image1.format());
    _verso = image;
    _recto = image;


    std::cout << w_length << " " << h_length << std::endl;
    std::cout << "Going to register images of size :" << _recto.width() << "x" << _recto.height() << std::endl;


    for(int i = 0 + w_length; i < image1.width()-w_length; i++)
    {
        for(int j=0+h_length; j < image1.height()-h_length; j++)
        {
            //std::cout << i << " image2 " << image2.width() << " j "<< j << " "<< i-w_length << " " << j-h_length << std::endl;
            _verso.setPixel(i-w_length,j-h_length,image2.getPixel(i,j));
            _recto.setPixel(i-w_length,j-h_length,image1.getPixel(i,j));
        }
    }
    Logger log;
    log.log(_verso,"verso");
    log.log(_recto,"recto");
}

/***********************************************************************************************************/
/***********************************************************************************************************/
/***********************************************************************************************************/
/*** ResizedRegistration */

ResizedRegistration::ResizedRegistration(const girl::tools::Image &image1, const girl::tools::Image &image2, int factor)
    : Registration(AffineTransform::resized(image1,factor), AffineTransform::resized(image2,factor))
{}
